package mylittleinvestigations;

import mylittleinvestigations.CaseInformation.CaseXmlWriter;
import mylittleinvestigations.CaseInformation.Case;
import mylittleinvestigations.CaseInformation.CaseXmlReader;
import mylittleinvestigations.CaseInformation.CaseParseException;
import mylittleinvestigations.GameUi.ButtonArray;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import mylittleinvestigations.Conversation.ContinuousAction;
import mylittleinvestigations.Conversation.SingleAction;
import mylittleinvestigations.EasingFunctions.*;
import mylittleinvestigations.Events.ButtonArrayEventProvider;
import mylittleinvestigations.Events.ButtonArrayEventListener;
import mylittleinvestigations.GameUi.ButtonArrayLoadParameters;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.UnicodeFont;

public class Confrontation extends Interrogation
{
    private static final int IconOffscreenOffset = 300;
    private static final double HealthNumberDamageScale = 0.6;
    private static final String TopicSelectionHeaderText = "Talk About...";

    private static Image confrontationHealthCircleTexture;
    private static UnicodeFont confrontationHealthNumberingFont;
    private static HashMap<String, ConfrontationTopic> currentConfrontationTopicsById;

    private HashMap<String, String> confrontationTopicNamesById;
    private BeginConfrontationTopicSelectionAction currentBeginConfrontationTopicSelectionAction;
    private int currentBeginConfrontationTopicSelectionActionIndex;
    private SkipPlayerDefeatedAction currentSkipPlayerDefeatedAction;

    private Sprite playerIcon;
    private Sprite opponentIcon;

    private EasingFunction iconOffsetEasingFunction;
    private int iconOffset;
    private EasingFunction playerHealthNumberScaleEasingFunction;
    private double playerHealthNumberScale;
    private EasingFunction playerHealthNumberNotRedRgbValueEasingFunction;
    private int playerHealthNumberNotRedRgbValue;
    private EasingFunction opponentHealthNumberScaleEasingFunction;
    private double opponentHealthNumberScale;
    private EasingFunction opponentHealthNumberNotRedRgbValueEasingFunction;
    private int opponentHealthNumberNotRedRgbValue;

    public Confrontation()
    {
        super();
        this.currentBeginConfrontationTopicSelectionAction = null;
        this.currentBeginConfrontationTopicSelectionActionIndex = -1;
        this.confrontationTopicNamesById = new HashMap<String, String>();

        this.opponentIconOffset = new Vector2(0,0);
        this.playerIconOffset = new Vector2(0,0);
    }

    @Override
    protected boolean getCanExit()
    {
        return false;
    }

    private Sprite getPlayerIcon()
    {
        if (this.playerIcon == null)
        {
            this.playerIcon = Case.getInstance().getSpriteManager().GetSpriteFromId(this.getPlayerIconTextureId());
        }

        return this.playerIcon;
    }

    private String playerIconTextureId;

    public String getPlayerIconTextureId() { return this.playerIconTextureId; }
    public void setPlayerIconTextureId(String playerIconTextureId) { this.playerIconTextureId = playerIconTextureId; }

    private Vector2 playerIconOffset;

    public Vector2 getPlayerIconOffset() { return this.playerIconOffset; }
    public void setPlayerIconOffset(Vector2 playerIconOffset) { this.playerIconOffset = playerIconOffset; }

    private int initialPlayerHealth;

    public int getInitialPlayerHealth() { return this.initialPlayerHealth; }
    public void setInitialPlayerHealth(int initialPlayerHealth) { this.initialPlayerHealth = initialPlayerHealth; }

    private int playerHealth;

    public int getPlayerHealth() { return this.playerHealth; }
    public void setPlayerHealth(int playerHealth) { this.playerHealth = playerHealth; }

    private Sprite getOpponentIcon()
    {
        if (this.opponentIcon == null)
        {
            this.opponentIcon = Case.getInstance().getSpriteManager().GetSpriteFromId(this.getOpponentIconTextureId());
        }

        return this.opponentIcon;
    }

    private String opponentIconTextureId;

    public String getOpponentIconTextureId() { return this.opponentIconTextureId; }
    public void setOpponentIconTextureId(String opponentIconTextureId) { this.opponentIconTextureId = opponentIconTextureId; }

    private Vector2 opponentIconOffset;

    public Vector2 getOpponentIconOffset() { return this.opponentIconOffset; }
    public void setOpponentIconOffset(Vector2 opponentIconOffset) { this.opponentIconOffset = opponentIconOffset; }

    private int initialOpponentHealth;

    public int getInitialOpponentHealth() { return this.initialOpponentHealth; }
    public void setInitialOpponentHealth(int initialOpponentHealth) { this.initialOpponentHealth = initialOpponentHealth; }

    private int opponentHealth;

    public int getOpponentHealth() { return this.opponentHealth; }
    public void setOpponentHealth(int opponentHealth) { this.opponentHealth = opponentHealth; }

    private int playerDefeatedIndex;

    public int getPlayerDefeatedIndex() { return this.playerDefeatedIndex; }
    public void setPlayerDefeatedIndex(int playerDefeatedIndex) { this.playerDefeatedIndex = playerDefeatedIndex; }

    public static void Initialize(Image confrontationHealthCircleTexture, UnicodeFont confrontationHealthNumberingFont)
    {
        Confrontation.confrontationHealthCircleTexture = confrontationHealthCircleTexture;
        Confrontation.confrontationHealthNumberingFont = confrontationHealthNumberingFont;
    }

    public static ConfrontationTopic GetConfrontationTopicFromId(String id)
    {
        ConfrontationTopic topic = null;

        if (currentConfrontationTopicsById != null)
        {
            topic = currentConfrontationTopicsById.get(id);
        }

        return topic;
    }

    @Override
    public void Begin(State state)
    {
        super.Begin(state);

        this.setPlayerHealth(this.getInitialPlayerHealth());
        this.setOpponentHealth(this.getInitialOpponentHealth());
        this.iconOffset = IconOffscreenOffset;

        this.playerHealthNumberScale = 1;
        this.playerHealthNumberNotRedRgbValue = 255;
        this.opponentHealthNumberScale = 1;
        this.opponentHealthNumberNotRedRgbValue = 255;
    }

    @Override
    public void Update(GameContainer gc, int delta) throws CaseParseException
    {
        super.Update(gc, delta);

        if (this.iconOffsetEasingFunction != null)
        {
            this.iconOffsetEasingFunction.Update(gc, delta);
            this.iconOffset = (int)this.iconOffsetEasingFunction.GetCurrentValue();

            if (this.iconOffsetEasingFunction.getIsFinished())
            {
                this.iconOffsetEasingFunction = null;
            }
        }

        if (this.playerHealthNumberScaleEasingFunction != null)
        {
            this.playerHealthNumberScaleEasingFunction.Update(gc, delta);
            this.playerHealthNumberScale = this.playerHealthNumberScaleEasingFunction.GetCurrentValue();

            if (this.playerHealthNumberScaleEasingFunction.getIsFinished())
            {
                this.playerHealthNumberScaleEasingFunction = null;
            }
        }

        if (this.playerHealthNumberNotRedRgbValueEasingFunction != null)
        {
            this.playerHealthNumberNotRedRgbValueEasingFunction.Update(gc, delta);
            this.playerHealthNumberNotRedRgbValue = (int)this.playerHealthNumberNotRedRgbValueEasingFunction.GetCurrentValue();

            if (this.playerHealthNumberNotRedRgbValueEasingFunction.getIsFinished())
            {
                this.playerHealthNumberNotRedRgbValueEasingFunction = null;
            }
        }

        if (this.opponentHealthNumberScaleEasingFunction != null)
        {
            this.opponentHealthNumberScaleEasingFunction.Update(gc, delta);
            this.opponentHealthNumberScale = this.opponentHealthNumberScaleEasingFunction.GetCurrentValue();

            if (this.opponentHealthNumberScaleEasingFunction.getIsFinished())
            {
                this.opponentHealthNumberScaleEasingFunction = null;
            }
        }

        if (this.opponentHealthNumberNotRedRgbValueEasingFunction != null)
        {
            this.opponentHealthNumberNotRedRgbValueEasingFunction.Update(gc, delta);
            this.opponentHealthNumberNotRedRgbValue = (int)this.opponentHealthNumberNotRedRgbValueEasingFunction.GetCurrentValue();

            if (this.opponentHealthNumberNotRedRgbValueEasingFunction.getIsFinished())
            {
                this.opponentHealthNumberNotRedRgbValueEasingFunction = null;
            }
        }
    }

    @Override
    public void Draw(GameContainer gc, Graphics g, float yOffset) throws SlickException
    {
        Vector2 healthIconLeftCenter = new Vector2(90, 90);
        Vector2 healthIconRightCenter = new Vector2(Settings.DisplayBounds.getX() - 90, 90);

        Vector2 circleOffset = new Vector2(confrontationHealthCircleTexture.getWidth() / 2, confrontationHealthCircleTexture.getHeight() / 2);
        Vector2 leftIconStandardOffset = new Vector2(this.getOpponentIcon().getWidth() / 2, this.getOpponentIcon().getHeight() / 2);
        Vector2 rightIconStandardOffset = new Vector2(this.getPlayerIcon().getWidth() / 2, this.getPlayerIcon().getHeight() / 2);
        Vector2 healthNumberOffset = new Vector2(0, 3);
        Vector2 horizontalOffset = new Vector2(this.iconOffset, 0);

        String playerHealthString = Integer.toString(this.getPlayerHealth());
        String opponentHealthString = Integer.toString(this.getOpponentHealth());

        Utils.drawImage(g, confrontationHealthCircleTexture, healthIconLeftCenter.subtract(circleOffset).subtract(horizontalOffset));
        this.getOpponentIcon().Draw(gc, g, healthIconLeftCenter.subtract(leftIconStandardOffset).add(this.getOpponentIconOffset()).subtract(horizontalOffset));
        
        // TODO Make this work with the following scale: this.opponentHealthNumberScale
        Utils.drawString(confrontationHealthNumberingFont, opponentHealthString, healthIconLeftCenter.subtract(new Vector2(confrontationHealthNumberingFont.getWidth(opponentHealthString), confrontationHealthNumberingFont.getHeight(opponentHealthString)).scalarMultiply((float)this.opponentHealthNumberScale / 2)).add(healthNumberOffset).subtract(horizontalOffset), new Color(255, this.opponentHealthNumberNotRedRgbValue, this.opponentHealthNumberNotRedRgbValue, 255));

        Utils.drawImage(g, confrontationHealthCircleTexture, healthIconRightCenter.subtract(circleOffset).add(horizontalOffset));
        this.getPlayerIcon().Draw(gc, g, healthIconRightCenter.subtract(rightIconStandardOffset).add(new Vector2(-this.getPlayerIconOffset().getX(), this.getPlayerIconOffset().getY())).add(horizontalOffset), Color.white, 1.0f /* scale */, true /* flipHorizontally */);
        
        // TODO Make this work with the following scale: this.playerHealthNumberScale
        Utils.drawString(confrontationHealthNumberingFont, playerHealthString, healthIconRightCenter.subtract(new Vector2(confrontationHealthNumberingFont.getWidth(playerHealthString), confrontationHealthNumberingFont.getHeight(playerHealthString)).scalarMultiply((float)this.opponentHealthNumberScale / 2)).add(healthNumberOffset).add(horizontalOffset), new Color(255, this.playerHealthNumberNotRedRgbValue, this.playerHealthNumberNotRedRgbValue, 255));

        super.Draw(gc, g, yOffset);
    }

    public void ShowHealthIcons()
    {
        this.iconOffsetEasingFunction = new QuadraticEase(IconOffscreenOffset, 0, 250);
        this.iconOffsetEasingFunction.Begin();
    }

    public void HideHealthIcons()
    {
        this.iconOffsetEasingFunction = new QuadraticEase(0, IconOffscreenOffset, 250);
        this.iconOffsetEasingFunction.Begin();
    }

    public void DamagePlayer()
    {
        if (this.getPlayerHealth() > 0)
        {
            this.setPlayerHealth(this.getPlayerHealth() - 1);

            this.playerHealthNumberScaleEasingFunction = new QuadraticEase(HealthNumberDamageScale, 1, 250);
            this.playerHealthNumberNotRedRgbValueEasingFunction = new LinearEase(0, 255, 250);
            this.playerHealthNumberScaleEasingFunction.Begin();
            this.playerHealthNumberNotRedRgbValueEasingFunction.Begin();

            if (this.getPlayerHealth() == 0)
            {
                this.getState().setActionIndex(this.getPlayerDefeatedIndex());
            }
        }
    }

    public void DamageOpponent()
    {
        if (this.getOpponentHealth() > 0)
        {
            this.setOpponentHealth(this.getOpponentHealth() - 1);

            this.opponentHealthNumberScaleEasingFunction = new QuadraticEase(HealthNumberDamageScale, 1, 250);
            this.opponentHealthNumberNotRedRgbValueEasingFunction = new LinearEase(0, 255, 250);
            this.opponentHealthNumberScaleEasingFunction.Begin();
            this.opponentHealthNumberNotRedRgbValueEasingFunction.Begin();
        }
    }
    
    public void SetParticipants(String playerCharacterId, String opponentCharacterId)
    {
        this.actionList.add(new SetParticipantsAction(playerCharacterId, opponentCharacterId));
    }
    
    public void SetPlayerIconOffset(Vector2 offset)
    {
        this.actionList.add(new SetIconOffsetAction(offset, true /* isPlayer */));
    }
    
    public void SetPlayerHealth(int initialHealth)
    {
        this.actionList.add(new SetHealthAction(initialHealth, true /* isPlayer */));
    }
    
    public void SetOpponentIconOffset(Vector2 offset)
    {
        this.actionList.add(new SetIconOffsetAction(offset, false /* isPlayer */));
    }
    
    public void SetOpponentHealth(int initialHealth)
    {
        this.actionList.add(new SetHealthAction(initialHealth, false /* isPlayer */));
    }

    public void BeginConfrontationTopicSelection() throws CaseParseException
    {
        this.actionList.add(new InitializeBeginConfrontationTopicSelectionAction());
        this.currentBeginConfrontationTopicSelectionActionIndex = this.actionList.size();

        BeginConfrontationTopicSelectionAction beginConfrontationTopicSelectionAction = new BeginConfrontationTopicSelectionAction();
        this.actionList.add(beginConfrontationTopicSelectionAction);

        if (this.currentBeginConfrontationTopicSelectionAction != null)
        {
            throw new CaseParseException("Cannot have nested BeginConfrontationTopicSelectionActions.");
        }

        this.currentBeginConfrontationTopicSelectionAction = beginConfrontationTopicSelectionAction;
    }

    public void BeginInitialConfrontationTopic()
    {
        ConfrontationTopic topic = new ConfrontationTopic(null, null, this.actionList.size());
        this.currentBeginConfrontationTopicSelectionAction.setInitialTopic(topic);
        this.actionList.add(new BeginConfrontationTopicAction());
    }

    public void EndInitialConfrontationTopic()
    {
        EndConfrontationTopicAction endConfrontationTopicAction = new EndConfrontationTopicAction();
        endConfrontationTopicAction.setBeginConfrontationTopicSelectionActionIndex(this.currentBeginConfrontationTopicSelectionActionIndex);
        this.actionList.add(endConfrontationTopicAction);
    }

    public void BeginConfrontationTopic(String id, String name)
    {
        ConfrontationTopic topic = new ConfrontationTopic(id, name, this.actionList.size());
        this.currentBeginConfrontationTopicSelectionAction.AddTopic(topic);
        this.actionList.add(new BeginConfrontationTopicAction());

        this.confrontationTopicNamesById.put(id, name);
    }

    public void EndConfrontationTopic()
    {
        EndConfrontationTopicAction endConfrontationTopicAction = new EndConfrontationTopicAction();
        endConfrontationTopicAction.setBeginConfrontationTopicSelectionActionIndex(this.currentBeginConfrontationTopicSelectionActionIndex);
        this.actionList.add(endConfrontationTopicAction);
    }

    public void BeginPlayerDefeated() throws CaseParseException
    {
        if (this.currentBeginConfrontationTopicSelectionAction == null)
        {
            throw new CaseParseException("BeginPlayerDefeated must occur within a BeginConfrontationTopicSelectionAction");
        }
        
        if (this.currentBeginConfrontationTopicSelectionAction.getPlayerDefeatedIndex() > 0)
        {
            throw new CaseParseException("Cannot have more than one BeginPlayerDefeatedAction.");
        }

        SkipPlayerDefeatedAction skipPlayerDefeatedAction = new SkipPlayerDefeatedAction();
        this.currentSkipPlayerDefeatedAction = skipPlayerDefeatedAction;

        this.actionList.add(skipPlayerDefeatedAction);
        this.currentBeginConfrontationTopicSelectionAction.setPlayerDefeatedIndex(this.actionList.size());
        this.actionList.add(new BeginPlayerDefeatedAction());
    }

    public void EndPlayerDefeated() throws CaseParseException
    {
        if (this.currentSkipPlayerDefeatedAction == null)
        {
            throw new CaseParseException("Cannot have an EndPlayerDefeatedAction without a BeginPlayerDefeatedAction.");
        }

        this.actionList.add(new EndPlayerDefeatedAction());
        this.currentSkipPlayerDefeatedAction.setSkipToIndex(this.actionList.size());
    }

    public void EndConfrontationTopicSelection() throws CaseParseException
    {
        if (this.currentBeginConfrontationTopicSelectionAction == null)
        {
            throw new CaseParseException("Cannot have an EndConfrontationTopicSelectionAction without first having a BeginConfrontationTopicSelectionAction");
        }

        for (int i = this.actionList.size() - 1; i >= 0; i--)
        {
            if (this.actionList.get(i) == this.currentBeginConfrontationTopicSelectionAction)
            {
                this.currentBeginConfrontationTopicSelectionAction.setEndActionIndex(this.actionList.size());
                break;
            }

            ShowInterrogationAction showInterrogationAction = Utils.as(this.actionList.get(i), ShowInterrogationAction.class);

            if (showInterrogationAction != null)
            {
                showInterrogationAction.setEndRequestedIndex(this.currentBeginConfrontationTopicSelectionActionIndex);
                showInterrogationAction.setIsInConfrontation(true);
            }
        }

        this.actionList.add(new EndConfrontationTopicSelectionAction());
        this.currentBeginConfrontationTopicSelectionActionIndex = -1;
        this.currentBeginConfrontationTopicSelectionAction = null;
    }
    
    public String GetTopicNameById(String topicId)
    {
        return this.confrontationTopicNamesById.get(topicId);
    }

    public void EnableTopic(String topicId, boolean notify)
    {
        this.actionList.add(new EnableTopicAction(topicId));

        NotificationAction notificationAction = new NotificationAction();

        if (notify)
        {
            this.actionList.add(notificationAction);
        }
        
        Case.getInstance().AddEnableTopicDependencyCheck(topicId, this, notificationAction);
    }
    
    public void RestartConfrontation()
    {
        this.actionList.add(new RestartConfrontationAction());
    }
    
    @Override
    public void SaveToXml(CaseXmlWriter writer) throws IOException
    {
        writer.StartElement("Confrontation");
        this.SaveToXmlCore(writer);
        writer.EndElement();
    }

    @Override
    protected void SaveToXmlCore(CaseXmlWriter writer) throws IOException
    {
        super.SaveToXmlCore(writer);
        
        writer.StartElement("ConfrontationTopicNamesById");
        String[] ids = this.confrontationTopicNamesById.keySet().toArray(new String[0]);
        
        for (int i = 0; i < ids.length; i++)
        {
            writer.StartElement("Entry");
            String id = ids[i];
            writer.WriteTextElement("Id", id);
            writer.WriteTextElement("ConfrontationTopicName", this.confrontationTopicNamesById.get(id));
            writer.EndElement();
        }
        
        //writer.WriteTextElement("PlayerIconTextureId", this.playerIconTextureId);
        //this.playerIconOffset.SaveToXml(writer);
        //writer.WriteIntElement("InitialPlayerHealth", this.initialPlayerHealth);
        //writer.WriteTextElement("OpponentIconTextureId", this.opponentIconTextureId);
        //this.opponentIconOffset.SaveToXml(writer);
        //writer.WriteIntElement("InitialOpponentHealth", this.initialOpponentHealth);
        
        writer.EndElement();
    }
    
    public Confrontation(CaseXmlReader reader)
    {
        this.confrontationTopicNamesById = new HashMap<String, String>();
        
        this.Initialize();
        reader.StartElement("Confrontation");
        this.LoadFromXmlCore(reader);
        reader.EndElement();
    }
    
    @Override
    protected void LoadFromXmlCore(CaseXmlReader reader)
    {
        super.LoadFromXmlCore(reader);
        
        reader.StartElement("ConfrontationTopicNamesById");
        reader.StartList("Entry");
        
        while (reader.MoveToNextListItem())
        {
            String id = reader.ReadTextElement("Id");
            String confrontationTopicName = reader.ReadTextElement("ConfrontationTopicName");
            this.confrontationTopicNamesById.put(id, confrontationTopicName);
        }
        
        this.playerIconTextureId = reader.ReadTextElement("PlayerIconTextureId");
        this.playerIconOffset = new Vector2(reader);
        this.initialPlayerHealth = reader.ReadIntElement("InitialPlayerHealth");
        this.opponentIconTextureId = reader.ReadTextElement("OpponentIconTextureId");
        this.opponentIconOffset = new Vector2(reader);
        this.initialOpponentHealth = reader.ReadIntElement("InitialOpponentHealth");
        this.playerDefeatedIndex = reader.ReadIntElement("PlayerDefeatedIndex");
        
        reader.EndElement();
    }
    
    @Override
    protected Action GetActionForNextElement(CaseXmlReader reader)
    {
        if (reader.ElementExists("InitializeBeginConfrontationTopicSelectionAction"))
        {
            return new InitializeBeginConfrontationTopicSelectionAction(reader);
        }
        else if (reader.ElementExists("BeginConfrontationTopicSelectionAction"))
        {
            return new BeginConfrontationTopicSelectionAction(reader);
        }
        else if (reader.ElementExists("BeginConfrontationTopicAction"))
        {
            return new BeginConfrontationTopicAction(reader);
        }
        else if (reader.ElementExists("EndConfrontationTopicAction"))
        {
            return new EndConfrontationTopicAction(reader);
        }
        else if (reader.ElementExists("SkipPlayerDefeatedAction"))
        {
            return new SkipPlayerDefeatedAction(reader);
        }
        else if (reader.ElementExists("BeginPlayerDefeatedAction"))
        {
            return new BeginPlayerDefeatedAction(reader);
        }
        else if (reader.ElementExists("EndPlayerDefeatedAction"))
        {
            return new EndPlayerDefeatedAction(reader);
        }
        else if (reader.ElementExists("EndConfrontationTopicSelectionAction"))
        {
            return new EndConfrontationTopicSelectionAction(reader);
        }
        else if (reader.ElementExists("EnableTopicAction"))
        {
            return new EnableTopicAction(reader);
        }
        else
        {
            return super.GetActionForNextElement(reader);
        }
    }

    protected class SetParticipantsAction extends SingleAction
    {
        private String playerCharacterId;
        private String opponentCharacterId;
        
        public SetParticipantsAction(String playerCharacterId, String opponentCharacterId)
        {
            this.playerCharacterId = playerCharacterId;
            this.opponentCharacterId = opponentCharacterId;
        }

        @Override
        public void Execute(State state)
        {
            // Set participants.
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SetParticipantsAction");
            writer.WriteTextElement("PlayerCharacterId", this.playerCharacterId);
            writer.WriteTextElement("OpponentCharacterId", this.opponentCharacterId);
            writer.EndElement();
        }

        private SetParticipantsAction(CaseXmlReader reader)
        {
            reader.StartElement("SetParticipantsAction");
            this.playerCharacterId = reader.ReadTextElement("PlayerCharacterId");
            this.opponentCharacterId = reader.ReadTextElement("OpponentCharacterId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SetParticipantsAction action = new SetParticipantsAction(this.playerCharacterId, this.opponentCharacterId);
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, Conversation.ActionState state) throws CaseParseException
        {
            DialogCharacter playerDialogCharacter = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(this.playerCharacterId);
            
            if (playerDialogCharacter != null)
            {
                playerDialogCharacter.markConfrontationChildSprites(markParent);
            }
            else
            {
                throw new CaseParseException("No character with ID '" + this.playerCharacterId + "'.");
            }
            
            DialogCharacter opponentDialogCharacter = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(this.opponentCharacterId);
            
            if (opponentDialogCharacter != null)
            {
                opponentDialogCharacter.markConfrontationChildSprites(markParent);
            }
            else
            {
                throw new CaseParseException("No character with ID '" + this.opponentCharacterId + "'.");
            }
        }
    }

    protected class SetIconOffsetAction extends SingleAction
    {
        private Vector2 offset;
        private boolean isPlayer;
        
        public SetIconOffsetAction(Vector2 offset, boolean isPlayer)
        {
            this.offset = offset;
            this.isPlayer = isPlayer;
        }

        @Override
        public void Execute(State state)
        {
            if (this.isPlayer)
            {
                state.getCurrentConfrontation().setPlayerIconOffset(this.offset);
            }
            else
            {
                state.getCurrentConfrontation().setOpponentIconOffset(this.offset);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SetIconOffsetAction");
            
            writer.StartElement("Offset");
            this.offset.SaveToXml(writer);
            writer.EndElement();
            
            writer.WriteBooleanElement("IsPlayer", this.isPlayer);
            writer.EndElement();
        }

        private SetIconOffsetAction(CaseXmlReader reader)
        {
            reader.StartElement("SetIconOffsetAction");
            
            reader.StartElement("Offset");
            this.offset = new Vector2(reader);
            reader.EndElement();
            
            this.isPlayer = reader.ReadBooleanElement("IsPlayer");
            
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SetIconOffsetAction action = new SetIconOffsetAction(this.offset, this.isPlayer);
            
            return action;
        }
    }

    protected class SetHealthAction extends SingleAction
    {
        private int initialHealth;
        private boolean isPlayer;
        
        public SetHealthAction(int initialHealth, boolean isPlayer)
        {
            this.initialHealth = initialHealth;
            this.isPlayer = isPlayer;
        }

        @Override
        public void Execute(State state)
        {
            if (this.isPlayer)
            {
                state.getCurrentConfrontation().setInitialPlayerHealth(this.initialHealth);
            }
            else
            {
                state.getCurrentConfrontation().setInitialOpponentHealth(this.initialHealth);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SetHealthAction");
            writer.WriteIntElement("InitialHealth", this.initialHealth);
            writer.WriteBooleanElement("IsPlayer", this.isPlayer);
            writer.EndElement();
        }

        private SetHealthAction(CaseXmlReader reader)
        {
            reader.StartElement("SetHealthAction");
            this.initialHealth = reader.ReadIntElement("InitialHealth");
            this.isPlayer = reader.ReadBooleanElement("IsPlayer");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SetHealthAction action = new SetHealthAction(this.initialHealth, this.isPlayer);
            
            return action;
        }
    }

    protected class InitializeBeginConfrontationTopicSelectionAction extends SingleAction
    {
        public InitializeBeginConfrontationTopicSelectionAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.getCurrentConfrontation().ShowHealthIcons();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("InitializeBeginConfrontationTopicSelectionAction");
            writer.EndElement();
        }

        private InitializeBeginConfrontationTopicSelectionAction(CaseXmlReader reader)
        {
            reader.StartElement("InitializeBeginConfrontationTopicSelectionAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            InitializeBeginConfrontationTopicSelectionAction action = new InitializeBeginConfrontationTopicSelectionAction();
            
            return action;
        }
    }

    protected class BeginConfrontationTopicSelectionAction extends ContinuousAction implements ButtonArrayEventListener
    {
        private ArrayList<ConfrontationTopic> topicList;
        private ButtonArray topicSelectionButtonArray;

        public BeginConfrontationTopicSelectionAction()
        {
            this.Initialize();
            this.initialTopic = null;
            this.topicList = new ArrayList<ConfrontationTopic>();
        }
        
        private ConfrontationTopic initialTopic;

        public ConfrontationTopic getInitialTopic() { return this.initialTopic; }
        public void setInitialTopic(ConfrontationTopic initialTopic) { this.initialTopic = initialTopic; }

        private int playerDefeatedIndex;

        public int getPlayerDefeatedIndex() { return this.playerDefeatedIndex; }
        public void setPlayerDefeatedIndex(int playerDefeatedIndex) { this.playerDefeatedIndex = playerDefeatedIndex; }
        
        private int endActionIndex;

        public int getEndActionIndex() { return this.endActionIndex; }
        public void setEndActionIndex(int endActionIndex) { this.endActionIndex = endActionIndex; }

        public void AddTopic(ConfrontationTopic topic)
        {
            this.topicList.add(topic);
        }

        public void Initialize()
        {
            this.topicSelectionButtonArray = new ButtonArray(TopicSelectionHeaderText);
            this.topicSelectionButtonArray.setIsCancelable(false);
        }

        @Override
        public void Begin(State state) throws CaseParseException
        {
            super.Begin(state);

            boolean allConversationsFinished = true;
            ArrayList<ButtonArrayLoadParameters> loadParametersList = new ArrayList<ButtonArrayLoadParameters>();

            currentConfrontationTopicsById = new HashMap<String, ConfrontationTopic>();

            for (int i = 0; i < this.topicList.size(); i++)
            {
                ConfrontationTopic topic = this.topicList.get(i);
                currentConfrontationTopicsById.put(topic.getId(), topic);

                if (!topic.getIsCompleted())
                {
                    allConversationsFinished = false;

                    if (topic.getIsEnabled())
                    {
                        ButtonArrayLoadParameters loadParameters = new ButtonArrayLoadParameters();
                        loadParameters.text = topic.getName();
                        loadParametersList.add(loadParameters);
                    }
                }
            }

            if (!allConversationsFinished)
            {
                this.topicSelectionButtonArray.Load(loadParametersList);
            }

            if (this.getInitialTopic() != null && !this.getInitialTopic().getIsCompleted())
            {
                this.state.setActionIndex(this.getInitialTopic().getActionIndex());
                this.state.setCurrentConfrontationTopic(this.getInitialTopic());
                this.setIsFinished(true);
            }
            else if (allConversationsFinished)
            {
                this.state.setActionIndex(this.getEndActionIndex());
                this.setIsFinished(true);
            }
            else
            {
                ButtonArrayEventProvider.RegisterListener(this);
                this.topicSelectionButtonArray.Show();
                this.setIsFinished(false);
            }
        }

        @Override
        public void Update(GameContainer gc,int delta)
        {
            if (this.getIsFinished())
            {
                return;
            }

            this.topicSelectionButtonArray.Update(gc,delta);

            if (this.topicSelectionButtonArray.getIsClosed())
            {
                this.setIsFinished(true);
            }
        }

        @Override
        public void Draw(GameContainer gc, Graphics g, float yOffset)
        {
            if (this.getIsFinished())
            {
                return;
            }

            this.topicSelectionButtonArray.Draw(gc, g, yOffset);
        }

        public void OnButtonArrayButtonClicked(ButtonArray sender, int id)
        {
            if (sender != this.topicSelectionButtonArray)
            {
                return;
            }
            
            // First we find the topic that was clicked on,
            // and then we move to that topic.
            for (int i = 0; i < this.topicList.size(); i++)
            {
                ConfrontationTopic topic = this.topicList.get(i);
                
                if (topic.getIsEnabled() && !topic.getIsCompleted())
                {
                    if (id == 0)
                    {
                        // This is the topic we're looking for.
                        // Move to its action index.
                        this.state.setCurrentConfrontationTopic(topic);
                        this.state.setActionIndex(topic.getActionIndex());
                        this.topicSelectionButtonArray.Close();
                        break;
                    }
                    else
                    {
                        // This isn't the topic we're looking for,
                        // but it IS a topic in the button array.
                        // Decrement id by 1 to bring us closer to
                        // the topic we're looking for.
                        id--;
                    }
                }
            }
        }
        
        public void OnButtonArrayCanceled(ButtonArray sender)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginConfrontationTopicSelectionAction");
            writer.WriteIntElement("PlayerDefeatedIndex", this.playerDefeatedIndex);
            writer.WriteIntElement("EndActionIndex", this.endActionIndex);
            
            if (this.initialTopic != null)
            {
                writer.StartElement("InitialTopic");
                this.initialTopic.SaveToXml(writer);
                writer.EndElement();
            }
            
            writer.StartElement("TopicList");
            
            for (int i = 0; i < this.topicList.size(); i++)
            {
                writer.StartElement("Entry");
                this.topicList.get(i).SaveToXml(writer);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.EndElement();
        }

        private BeginConfrontationTopicSelectionAction(CaseXmlReader reader)
        {
            this.Initialize();
            this.initialTopic = null;
            this.topicList = new ArrayList<ConfrontationTopic>();
            
            reader.StartElement("BeginConfrontationTopicSelectionAction");
            this.endActionIndex = reader.ReadIntElement("EndActionIndex");
            
            if (reader.ElementExists("InitialTopic"))
            {
                reader.StartElement("InitialTopic");
                this.initialTopic = new ConfrontationTopic(reader);
                reader.EndElement();
            }
            
            reader.StartElement("TopicList");
            reader.StartList("Entry");
            
            while (reader.MoveToNextListItem())
            {
                this.topicList.add(new ConfrontationTopic(reader));
            }
            
            reader.EndElement();
            
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginConfrontationTopicSelectionAction action = new BeginConfrontationTopicSelectionAction();
            
            action.setEndActionIndex(this.endActionIndex + indexOffset);
            action.setInitialTopic(this.initialTopic);
            action.setPlayerDefeatedIndex(this.playerDefeatedIndex + indexOffset);
            
            for (ConfrontationTopic topic : this.topicList)
            {
                action.AddTopic(topic);
            }
            
            return action;
        }
    }

    protected class BeginConfrontationTopicAction extends SingleAction
    {
        public BeginConfrontationTopicAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginConfrontationTopicAction");
            writer.EndElement();
        }
        
        private BeginConfrontationTopicAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginConfrontationTopicAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginConfrontationTopicAction action = new BeginConfrontationTopicAction();
            
            return action;
        }
    }

    protected class EndConfrontationTopicAction extends SingleAction
    {
        public EndConfrontationTopicAction()
        {
            this.beginConfrontationTopicSelectionActionIndex = -1;
        }

        private int beginConfrontationTopicSelectionActionIndex;

        public int getBeginConfrontationTopicSelectionActionIndex() { return this.beginConfrontationTopicSelectionActionIndex; }
        public void setBeginConfrontationTopicSelectionActionIndex(int beginConfrontationTopicSelectionActionIndex) { this.beginConfrontationTopicSelectionActionIndex = beginConfrontationTopicSelectionActionIndex; }

        @Override
        public void Execute(State state)
        {
            state.getCurrentConfrontationTopic().setIsCompleted(true);
            state.setCurrentConfrontationTopic(null);
            state.setActionIndex(this.getBeginConfrontationTopicSelectionActionIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndConfrontationTopicAction");
            writer.WriteIntElement("BeginConfrontationTopicSelectionActionIndex", this.beginConfrontationTopicSelectionActionIndex);
            writer.EndElement();
        }

        private EndConfrontationTopicAction(CaseXmlReader reader)
        {
            reader.StartElement("EndConfrontationTopicAction");
            this.beginConfrontationTopicSelectionActionIndex = reader.ReadIntElement("BeginConfrontationTopicSelectionActionIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndConfrontationTopicAction action = new EndConfrontationTopicAction();
            
            action.setBeginConfrontationTopicSelectionActionIndex(this.beginConfrontationTopicSelectionActionIndex + indexOffset);
            
            return action;
        }
    }

    protected class SkipPlayerDefeatedAction extends SingleAction
    {
        public SkipPlayerDefeatedAction()
        {
            this.skipToIndex = -1;
        }
        
        private int skipToIndex;

        public int getSkipToIndex() { return this.skipToIndex; }
        public void setSkipToIndex(int skipToIndex) { this.skipToIndex = skipToIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getSkipToIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SkipPlayerDefeatedAction");
            writer.WriteIntElement("SkipToIndex", this.skipToIndex);
            writer.EndElement();
        }

        private SkipPlayerDefeatedAction(CaseXmlReader reader)
        {
            reader.StartElement("SkipPlayerDefeatedAction");
            this.skipToIndex = reader.ReadIntElement("SkipToIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SkipPlayerDefeatedAction action = new SkipPlayerDefeatedAction();
            
            action.setSkipToIndex(this.skipToIndex + indexOffset);
            
            return action;
        }
    }

    protected class BeginPlayerDefeatedAction extends SingleAction
    {
        public BeginPlayerDefeatedAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginPlayerDefeatedAction");
            writer.EndElement();
        }

        private BeginPlayerDefeatedAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginPlayerDefeatedAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginPlayerDefeatedAction action = new BeginPlayerDefeatedAction();
            
            return action;
        }
    }

    protected class EndPlayerDefeatedAction extends SingleAction
    {
        public EndPlayerDefeatedAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.setEndRequested(true);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndPlayerDefeatedAction");
            writer.EndElement();
        }

        private EndPlayerDefeatedAction(CaseXmlReader reader)
        {
            reader.StartElement("EndPlayerDefeatedAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndPlayerDefeatedAction action = new EndPlayerDefeatedAction();
            
            return action;
        }
    }

    protected class EndConfrontationTopicSelectionAction extends SingleAction
    {
        public EndConfrontationTopicSelectionAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            currentConfrontationTopicsById = null;
            state.getCurrentConfrontation().HideHealthIcons();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndConfrontationTopicSelectionAction");
            writer.EndElement();
        }

        private EndConfrontationTopicSelectionAction(CaseXmlReader reader)
        {
            reader.StartElement("EndConfrontationTopicSelectionAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndConfrontationTopicSelectionAction action = new EndConfrontationTopicSelectionAction();
            
            return action;
        }
    }

    protected class EnableTopicAction extends SingleAction
    {
        private String topicId;

        public EnableTopicAction(String topicId)
        {
            this.topicId = topicId;
        }

        @Override
        public void Execute(State state)
        {
            ConfrontationTopic topic = null;

            if (Confrontation.currentConfrontationTopicsById.containsKey(this.topicId))
            {
                topic = Confrontation.currentConfrontationTopicsById.get(this.topicId);
                topic.setIsEnabled(true);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EnableTopicAction");
            writer.WriteTextElement("TopicId", this.topicId);
            writer.EndElement();
        }

        private EnableTopicAction(CaseXmlReader reader)
        {
            reader.StartElement("EnableTopicAction");
            this.topicId = reader.ReadTextElement("TopicId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EnableTopicAction action = new EnableTopicAction(this.topicId);
            
            return action;
        }
    }

    protected class RestartConfrontationAction extends ContinuousAction
    {
        public RestartConfrontationAction()
        {
        }

        @Override
        public void Update(GameContainer gc, int delta)
        {
        }

        @Override
        public void Draw(GameContainer gc, Graphics g, float yOffset) throws SlickException
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("RestartConfrontationAction");
            writer.EndElement();
        }

        private RestartConfrontationAction(CaseXmlReader reader)
        {
            reader.StartElement("RestartConfrontationAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            RestartConfrontationAction action = new RestartConfrontationAction();
            
            return action;
        }
    }
}
